﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Media;
using System.Net;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading;
using LumiSoft.Net;
using LumiSoft.Net.Media;
using LumiSoft.Net.RTP;
using LumiSoft.Net.SDP;
using LumiSoft.Net.SIP.Message;
using LumiSoft.Net.SIP.Stack;
using TEL.ClientClasses.Resources;
using TEL.WinMediaWPF.Audio;
using TEL.WinMediaWPF.Audio.Wav;
using TEL.WinMediaWPF.DTMF;

namespace TEL.ClientClasses.Private
{
    /// <summary>
    /// This class represent SIP UA call. 
    /// </summary>
    public class Call : IDisposable
    {
        private object m_pLock = new object();
        private CallState m_State = CallState.WaitingForStart;
        private SIP_Stack m_pStack = null;
        private SIP_Request m_pInvite = null;
        private DateTime m_StartTime = DateTime.Now;
        private List<SIP_Dialog> m_pEarlyDialogs = null;
        private SIP_Dialog_Invite m_pDialog = null;
        private bool m_IsRedirected = false;
        private SIP_RequestSender m_pInitialInviteSender = null;
        private SIP_ServerTransaction m_pInitialInviteTransaction = null;
        private AbsoluteUri m_pLocalUri = null;
        private AbsoluteUri m_pRemoteUri = null;
        private Dictionary<string, object> m_pTags = null;
        private WavePlayer m_pAudioPlayer = null;
     
        //private SoundPlayer m_player = null;
        private int mSignalVolume = 100;
        public int SignalVolume
        {
            get { return mSignalVolume; }
            set
            {
                mSignalVolume = value;
                if (m_pAudioPlayer != null) m_pAudioPlayer.Valume = value;
            }
        }

       private AudioOutDevice m_audioOutStram = null;
        public AudioOutDevice AudioOutStram
        {
            get { return m_audioOutStram; }
            set { m_audioOutStram = value; }
        }

        public static string AudioReceiver = "AudioReceiver";
        public static string AudioSender = "AudioSender";
        public static string MediaSession = "MediaSession";

        /// <summary>
        /// Default outgoing call constructor.
        /// </summary>
        /// <param name="stack">Owner SIP stack.</param>
        /// <param name="invite">INVITE request.</param>
        /// <exception cref="ArgumentNullException">Is riased when <b>ua</b> or <b>invite</b> is null reference.</exception>
        /// <exception cref="ArgumentException">Is raised when any of the arguments has invalid value.</exception>
        internal Call(SIP_Stack stack, SIP_Request invite)
        {
            if (stack == null)
            {
                throw new ArgumentNullException("stack");
            }
            if (invite == null)
            {
                throw new ArgumentNullException("invite");
            }
            if (invite.RequestLine.Method != SIP_Methods.INVITE)
            {
                throw new ArgumentException("Argument 'invite' is not INVITE request.");
            }

            m_pStack = stack;
            m_pInvite = invite;
            m_pLocalUri = invite.From.Address.Uri;
            m_pRemoteUri = invite.To.Address.Uri;
            IsWasActive = false;

            m_State = CallState.WaitingForStart;

            m_pEarlyDialogs = new List<SIP_Dialog>();
            m_pTags = new Dictionary<string, object>();
        }

        /// <summary>
        /// Default incoming call constructor.
        /// </summary>
        /// <param name="stack">Owner SIP stack.</param>
        /// <param name="invite">INVITE server transaction.</param>
        /// <exception cref="ArgumentNullException">Is riased when <b>ua</b> or <b>invite</b> is null reference.</exception>
        internal Call(SIP_Stack stack, SIP_ServerTransaction invite)
        {
            if (stack == null)
            {
                throw new ArgumentNullException("stack");
            }
            if (invite == null)
            {
                throw new ArgumentNullException("invite");
            }

            m_pStack = stack;
            m_pInitialInviteTransaction = invite;
            m_pLocalUri = invite.Request.To.Address.Uri;
            m_pRemoteUri = invite.Request.From.Address.Uri;
           
            m_pInitialInviteTransaction.Canceled += new EventHandler(delegate(object sender, EventArgs e)
            {
                // If transaction canceled, terminate call.
                SetState(CallState.Terminated);
            });
            m_pInitialInviteTransaction.StateChanged += new EventHandler(m_pInitialInviteTransaction_StateChanged);
            m_pTags = new Dictionary<string, object>();

            this.CallID = invite.Request.CallID;
            this.To = invite.Request.To;
            this.From = invite.Request.From;

            m_State = CallState.WaitingToAccept;
        }

        void m_pInitialInviteTransaction_StateChanged(object sender, EventArgs e)
        {
        }

        #region method Dispose

        /// <summary>
        /// Cleans up any resource being used.
        /// </summary>
        public void Dispose()
        {
            lock (m_pLock)
            {
                if (m_State == CallState.Disposed)
                {
                    return;
                }
                SetState(CallState.Disposed);

                // TODO: Clean up

                this.StateChanged = null;
            }
        }

        #endregion


        #region Events handling

        #region method m_pDialog_StateChanged

        /// <summary>
        /// Is called when SIP dialog state has changed.
        /// </summary>
        /// <param name="sender">Sender.</param>
        /// <param name="e">Event data.</param>
        private void m_pDialog_StateChanged(object sender, EventArgs e)
        {
            if (this.State == CallState.Terminated)
            {
                return;
            }

            if (m_pDialog.State == SIP_DialogState.Terminated)
            {
                SetState(CallState.Terminated);

                m_pDialog.Dispose();
            }
        }

        #endregion


        #region method m_pInitialInviteSender_ResponseReceived

        /// <summary>
        /// This method is called when initial INVITE sender got response.
        /// </summary>
        /// <param name="sender">Sender.</param>
        /// <param name="e">Event data.</param>
        private void m_pInitialInviteSender_ResponseReceived(object sender, SIP_ResponseReceivedEventArgs e)
        {
            try
            {
               // if (LogEvent != null) LogEvent(this, new LogEventArgs(LogType.Message, "исходящий_ResponseReceived", ""));
                if (LogEvent != null) LogEvent(this, new LogEventArgs(LogType.Message, "Call Responce", e.Response.ToString()));
                lock (m_pLock)
                {
                    if (e.Response.StatusCodeType == SIP_StatusCodeType.Provisional)
                    {
                        if (e.Response.StatusCode == 180)
                        {
                            SetState(CallState.Ringing);
                        }
                        else if (e.Response.StatusCode == 182)
                        {
                            SetState(CallState.Queued);
                        }
                        // We don't care other status responses.

                        /* RFC 3261 13.2.2.1.
                            Zero, one or multiple provisional responses may arrive before one or
                            more final responses are received.  Provisional responses for an
                            INVITE request can create "early dialogs".  If a provisional response
                            has a tag in the To field, and if the dialog ID of the response does
                            not match an existing dialog, one is constructed using the procedures
                            defined in Section 12.1.2.
                        */
                        if (e.Response.StatusCode > 100 && e.Response.To.Tag != null)
                        {
                            m_pEarlyDialogs.Add(m_pStack.TransactionLayer.GetOrCreateDialog(e.ClientTransaction, e.Response));
                        }
                    }
                    else if (e.Response.StatusCodeType == SIP_StatusCodeType.Success)
                    {
                        this.CallID = e.Response.CallID;
                        this.To = e.Response.To;
                        this.From = e.Response.From;

                        // m_StartTime = DateTime.Now;
                       // SetState(CallState.Active);

                        m_pDialog = (SIP_Dialog_Invite)m_pStack.TransactionLayer.GetOrCreateDialog(e.ClientTransaction, e.Response);
                        m_pDialog.StateChanged += new EventHandler(m_pDialog_StateChanged);

                        /* Exit all all other dialogs created by this call (due to forking).
                           That is not defined in RFC but, since UAC can send BYE to early and confirmed dialogs, 
                           all this is 100% valid.
                        */
                        foreach (SIP_Dialog dialog in m_pEarlyDialogs.ToArray())
                        {
                            if (!m_pDialog.Equals(dialog))
                            {
                                dialog.Terminate();
                            }
                        }

                        // If remote party provided SDP, parse it.
                        SDP_Message sdp = null;
                        if (e.Response.ContentType != null && e.Response.ContentType.ToLower().IndexOf("application/sdp") > -1)
                        {
                            sdp = SDP_Message.Parse(Encoding.UTF8.GetString(e.Response.Data));

                            // TODO: If parsing failed, end call.
                            // Probably call Error event handler should end the call.
                        }

                        // 2xx response maut always contain SDP answer or offer(if initial INVITE was offerless).
                        if (sdp == null)
                        {
                            m_pDialog.Terminate();
                        }
                        else
                        {
                            OnMediaAnswer(sdp);
                        }
                        SetState(CallState.Active);
                    }
                    else if (e.Response.StatusCodeType == SIP_StatusCodeType.RequestFailure)
                    {
                        SetState(CallState.Terminated);
                    }
                    else
                    {
                        /* RFC 3261 13.2.2.3.
                            All early dialogs are considered terminated upon reception of the non-2xx final response.
                        */
                        foreach (SIP_Dialog dialog in m_pEarlyDialogs.ToArray())
                        {
                            dialog.Terminate();
                        }
                        m_pEarlyDialogs.Clear();

                        //  Notify that call failed/rejected.
                        OnCallFailed(e);

                        SetState(CallState.Terminated);
                    }
                }
            }
            catch (Exception x)
            {
                OnError(x);
            }
        }

        #endregion

        #endregion


        #region method Start

        /// <summary>
        /// Starts calling.
        /// </summary>
        /// <exception cref="ObjectDisposedException">Is raised when this object is disposed and and this method is accessed.</exception>
        /// <exception cref="InvalidOperationException">Is raised when call is not in valid state.</exception>
        public void Start()
        {
            lock (m_pLock)
            {
                if (m_State == CallState.Disposed)
                {
                    throw new ObjectDisposedException(this.GetType().Name);
                }
                if (m_State != CallState.WaitingForStart)
                {
                    throw new InvalidOperationException("Start method can be called only in 'SIP_UA_CallState.WaitingForStart' state.");
                }

                SetState(CallState.Calling);

                m_pInitialInviteSender = m_pStack.CreateRequestSender(m_pInvite);
                m_pInitialInviteSender.Disposed += new EventHandler(m_pInitialInviteSender_Disposed);
                m_pInitialInviteSender.ResponseReceived += new EventHandler<SIP_ResponseReceivedEventArgs>(m_pInitialInviteSender_ResponseReceived);
                if(LogEvent!= null) LogEvent(this, new LogEventArgs(LogType.Message, "исходящий","Sender  готов"));
                m_pInitialInviteSender.Start();
                if (LogEvent != null) LogEvent(this, new LogEventArgs(LogType.Message, "исходящий", "Sender  отправлен"));
              
                // Start calling sound.
                m_pAudioPlayer = new WavePlayer(AudioOutStram);
                m_pAudioPlayer.Valume = SignalVolume;
                if (LogEvent != null) LogEvent(this, new LogEventArgs(LogType.Message, "исходящий", "запуск гудка"));
                m_pAudioPlayer.Play(ResManager.GetStream("calling.wav"), int.MaxValue);
                if (LogEvent != null) LogEvent(this, new LogEventArgs(LogType.Message, "исходящий", "гудок запущен"));
                //m_player = new SoundPlayer(ResManager.GetStream("calling.wav"));
                //m_player.PlayLooping();
            }
        }

        void m_pInitialInviteSender_Disposed(object sender, EventArgs e)
        {
            if (LogEvent!= null)LogEvent(this, new LogEventArgs(LogType.Info, "CallRequestSender"," Объкт уничтожен."));
        }

        #endregion
        
        #region method SendRinging

        /// <summary>
        /// Sends ringing to remote party.
        /// </summary>
        /// <param name="sdp">Early media answer or early media offer when initial INVITE don't have SDP.</param>
        /// <exception cref="ObjectDisposedException">Is raised when this object is disposed and and this method is accessed.</exception>
        /// <exception cref="InvalidOperationException">Is raised when call is not in valid state and this method is called.</exception>
        public void SendRinging(SDP_Message sdp)
        {
            if (m_State == CallState.Disposed)
            {
                throw new ObjectDisposedException(this.GetType().Name);
            }
            if (m_State != CallState.WaitingToAccept)
            {
                throw new InvalidOperationException("Accept method can be called only in 'SIP_UA_CallState.WaitingToAccept' state.");
            }

            SIP_Response response = m_pStack.CreateResponse(SIP_ResponseCodes.x180_Ringing, m_pInitialInviteTransaction.Request, m_pInitialInviteTransaction.Flow);
            if (sdp != null)
            {
                response.ContentType = "application/sdp";
                response.Data = sdp.ToByte();
            }
            m_pInitialInviteTransaction.SendResponse(response);

            // Start ringing sound.
            m_pAudioPlayer = new WavePlayer(AudioOutStram);
            m_pAudioPlayer.Valume = SignalVolume;
            m_pAudioPlayer.Play(ResManager.GetStream("ringing.wav"), int.MaxValue);

            //m_player = new SoundPlayer(ResManager.GetStream("ringing.wav"));
            //m_player.PlayLooping();
        }

        #endregion

        #region method Accept

        /// <summary>
        /// Accepts call.
        /// </summary>
        /// <param name="sdp">Media answer.</param>
        /// <exception cref="ObjectDisposedException">Is raised when this object is disposed and and this method is accessed.</exception>
        /// <exception cref="InvalidOperationException">Is raised when call is not in valid state and this method is called.</exception>
        /// <exception cref="ArgumentNullException">Is raised when <b>sdp</b> is null reference.</exception>
        public void Accept(SDP_Message sdp)
        {
            if (m_State == CallState.Disposed)
            {
                throw new ObjectDisposedException(this.GetType().Name);
            }
            if (m_State != CallState.WaitingToAccept)
            {
                throw new InvalidOperationException("Accept method can be called only in 'SIP_UA_CallState.WaitingToAccept' state.");
            }
            if (sdp == null)
            {
                throw new ArgumentNullException("sdp");
            }

            SIP_Response response = m_pStack.CreateResponse(SIP_ResponseCodes.x200_Ok, m_pInitialInviteTransaction.Request, m_pInitialInviteTransaction.Flow);
            response.ContentType = "application/sdp";
            response.Data = sdp.ToByte();
            m_pInitialInviteTransaction.SendResponse(response);
            
            m_pDialog = (SIP_Dialog_Invite)m_pStack.TransactionLayer.GetOrCreateDialog(m_pInitialInviteTransaction, response);
            m_pDialog.StateChanged += new EventHandler(m_pDialog_StateChanged);
            //   m_pDialog.Reinvite += new EventHandler<SIP_RequestReceivedEventArgs>(m_pDialog_Reinvite);
          
            SetState(CallState.Active);
        }

        void m_pDialog_Reinvite(object sender, SIP_RequestReceivedEventArgs e)
        {
            throw new NotImplementedException();
        }

        #endregion

        #region method Reject

        /// <summary>
        /// Rejects incoming call.
        /// </summary>
        /// <param name="statusCode_reason">Status-code reasonText.</param>
        /// <exception cref="ObjectDisposedException">Is raised when this object is disposed and and this method is accessed.</exception>
        /// <exception cref="InvalidOperationException">Is raised when call is not in valid state and this method is called.</exception>
        /// <exception cref="ArgumentNullException">Is raised when <b>statusCode_reason</b> is null.</exception>
        public void Reject(string statusCode_reason)
        {
            lock (m_pLock)
            {
                if (this.State == CallState.Disposed)
                {
                    throw new ObjectDisposedException(this.GetType().Name);
                }
                if (this.State != CallState.WaitingToAccept)
                {
                    throw new InvalidOperationException("Call is not in valid state.");
                }
                if (statusCode_reason == null)
                {
                    throw new ArgumentNullException("statusCode_reason");
                }

                m_pInitialInviteTransaction.SendResponse(m_pStack.CreateResponse(statusCode_reason, m_pInitialInviteTransaction.Request));

                SetState(CallState.Terminated);
            }
        }

        #endregion

        #region method Redirect

        /// <summary>
        /// Redirects incoming call to speified contact(s).
        /// </summary>
        /// <param name="contacts">Redirection targets.</param>
        /// <exception cref="ObjectDisposedException">Is raised when this object is disposed and and this method is accessed.</exception>
        /// <exception cref="InvalidOperationException">Is raised when call is not in valid state and this method is called.</exception>
        /// <exception cref="ArgumentNullException">Is raised when <b>contacts</b> is null.</exception>
        /// <exception cref="ArgumentException">Is raised when any of the arguments has invalid value.</exception>
        public void Redirect(SIP_t_ContactParam[] contacts)
        {
            lock (m_pLock)
            {
                if (this.State == CallState.Disposed)
                {
                    throw new ObjectDisposedException(this.GetType().Name);
                }
                if (this.State != CallState.WaitingToAccept)
                {
                    throw new InvalidOperationException("Call is not in valid state.");
                }
                if (contacts == null)
                {
                    throw new ArgumentNullException("contacts");
                }
                if (contacts.Length == 0)
                {
                    throw new ArgumentException("Arguments 'contacts' must contain at least 1 value.");
                }

                // TODO:
                //m_pUA.Stack.CreateResponse(SIP_ResponseCodes.,m_pInitialInviteTransaction);

                throw new NotImplementedException();
            }
        }

        #endregion


        #region method Terminate

        /// <summary>
        /// Starts terminating call. To get when call actually terminates, monitor <b>StateChanged</b> event.
        /// </summary>
        /// <exception cref="ObjectDisposedException">Is raised when this object is disposed and and this method is accessed.</exception>
        public void Terminate()
        {
            Terminate(null);
        }

        public void SetVolume(int volume)
        {
            try
            {
                RTP_AudioPlayer audioPlayer = Tags[AudioReceiver] as RTP_AudioPlayer;
                if (audioPlayer != null)
                {
                    audioPlayer.Volume = volume;
                }
            }
            catch(Exception){}
        }
    
        /// <summary>
        /// Starts terminating call. To get when call actually terminates, monitor <b>StateChanged</b> event.
        /// </summary>
        /// <param name="reason">Call termination reason.</param>
        /// <exception cref="ObjectDisposedException">Is raised when this object is disposed and and this method is accessed.</exception>
        public void Terminate(string reason)
        {
            lock (m_pLock)
            {
              //  StopPlayer();
                try
                {

                    MediaSession mediaSession = Tags[MediaSession] as MediaSession;
                    if (mediaSession != null)
                    {
                        mediaSession.MediaStreams.ForEach(x => x.RtpSession.Dispose());
                       Tags.Remove(MediaSession);
                    }
                    RTP_AudioSender audioSender = Tags[AudioSender] as RTP_AudioSender;
                    if (audioSender != null)
                    {
                        audioSender.Dispose();
                        Tags.Remove(AudioSender);
                    }

                    RTP_AudioPlayer audioPlayer = Tags[AudioReceiver] as RTP_AudioPlayer;
                    if (audioPlayer != null)
                    {
                        audioPlayer.Dispose();
                        Tags.Remove(AudioReceiver);
                    }
                }
                catch (Exception)
                {
                }

                IsTerminate = true;
                if (m_State == CallState.Disposed)
                {
                    throw new ObjectDisposedException(this.GetType().Name);
                }

                if (m_State == CallState.Terminating || m_State == CallState.Terminated)
                {
                    return;
                }
                else if (m_State == CallState.WaitingForStart)
                {
                    SetState(CallState.Terminated);
                }
                else if (m_State == CallState.WaitingToAccept)
                {
                    m_pInitialInviteTransaction.SendResponse(m_pStack.CreateResponse(SIP_ResponseCodes.x487_Request_Terminated, m_pInitialInviteTransaction.Request));

                    SetState(CallState.Terminated);
                }
                else if (m_State == CallState.Active)
                {
                    m_pDialog.Terminate(reason, true);

                    SetState(CallState.Terminated);
                }
                else if (m_pInitialInviteSender != null)
                {
                    /* RFC 3261 15.
                        If we are caller and call is not active yet, we must do following actions:
                            *) Send CANCEL, set call Terminating flag.
                            *) If we get non 2xx final response, we are done. (Normally cancel causes '408 Request terminated')
                            *) If we get 2xx response (2xx sent by remote party before our CANCEL reached), we must send BYE to active dialog.
                    */

                    SetState(CallState.Terminating);

                    m_pInitialInviteSender.Cancel();
                }
            }
        }

        #endregion

        public void StopPlayer()
        {
            if (m_pAudioPlayer != null)
            {
                m_pAudioPlayer.Stop();
                m_pAudioPlayer = null;
            }

            //if (m_player != null)
            //{
            //    m_player.Stop();
            //    m_player.Dispose();
            //}
        }

        #region method SetState

        /// <summary>
        /// Changes call state.
        /// </summary>
        /// <param name="state">New call state.</param>
        private void SetState(CallState state)
        {
            // Disposed call may not change state.
            if (m_State == CallState.Disposed)
            {
                return;
            }

            m_State = state;
            IsWasActive = IsWasActive || state == CallState.Active;
            if (state == CallState.Active || state == CallState.Terminated)
            {
                StopPlayer();
            }

            OnStateChanged(state);

            if (state == CallState.Terminated)
            {
                Dispose();
            }
        }

        #endregion

        #region Properties implementation

        /// <summary>
        /// Gets if this object is disposed.
        /// </summary>
        public bool IsDisposed
        {
            get { return m_State == CallState.Disposed; }
        }

        /// <summary>
        /// Gets current call state.
        /// </summary>
        public CallState State
        {
            get { return m_State; }
        }

        /// <summary>
        /// Gets call start time.
        /// </summary>
        public DateTime StartTime
        {
            get { return m_StartTime; }
        }

        /// <summary>
        /// Gets call local party URI.
        /// </summary>
        public AbsoluteUri LocalUri
        {
            get { return m_pLocalUri; }
        }

        /// <summary>
        /// Gets call remote party URI.
        /// </summary>
        public AbsoluteUri RemoteUri
        {
            get { return m_pRemoteUri; }
        }

        /// <summary>
        /// Gets call duration in seconds.
        /// </summary>
        public int Duration
        {
            get
            {
                return ((TimeSpan)(DateTime.Now - m_StartTime)).Seconds;

                // TODO: if terminated, we need static value here.
            }
        }

        /// <summary>
        /// Gets if call has been redirected by remote party.
        /// </summary>
        public bool IsRedirected
        {
            get { return m_IsRedirected; }
        }

        /// <summary>
        /// Gets if call is on hold.
        /// </summary>
        public bool IsOnhold
        {
            // TODO:

            get { return false; }
        }

        /// <summary>
        /// Gets user data items collection.
        /// </summary>
        public Dictionary<string, object> Tags
        {
            get { return m_pTags; }
        }


        internal SIP_Dialog_Invite Dialog
        {
            get { return m_pDialog; }
        }

        #endregion

        #region Events implementation

        /// <summary>
        /// Is raised when call state has changed.
        /// </summary>
        public event EventHandler StateChanged = null;

        #region method OnStateChanged

        /// <summary>
        /// Raises <b>StateChanged</b> event.
        /// </summary>
        /// <param name="state">New call state.</param>
        private void OnStateChanged(CallState state)
        {
            if (this.StateChanged != null)
            {
                this.StateChanged(this, new EventArgs());
            }
        }

        #endregion

        /// <summary>
        /// Is raised when unhandled exception occurs.
        /// </summary>
        public event EventHandler<ExceptionEventArgs> Error = null;

        #region method Error

        /// <summary>
        /// Raises 'Error' event.
        /// </summary>
        /// <param name="x">Exception.</param>
        private void OnError(Exception x)
        {
            if (this.Error != null)
            {
                this.Error(this, new ExceptionEventArgs(x));
            }
        }

        #endregion

        public event EventHandler<LogEventArgs> LogEvent = null;

        /// <summary>
        /// Is raised when call has failed/rejected by remote party.
        /// </summary>
        public event EventHandler<SIP_ResponseReceivedEventArgs> CallFailed = null;

        #region method OnCallFailed

        /// <summary>
        /// Raises 'CallFailed' event.
        /// </summary>
        /// <param name="e">SIP response recieved data.</param>
        private void OnCallFailed(SIP_ResponseReceivedEventArgs e)
        {
            if (this.CallFailed != null)
            {
                this.CallFailed(this, e);
            }
        }

        #endregion

        /// <summary>
        /// Is raised when media answer is received to our earlier sent offer.
        /// </summary>
        public event EventHandler<EventArgs<SDP_Message>> MediaAnswer = null;

        #region method OnMediaAnswer

        /// <summary>
        /// Raises <b>MediaAnswer</b> event.
        /// </summary>
        /// <param name="sdp">SDP answer.</param>
        private void OnMediaAnswer(SDP_Message sdp)
        {
            if (this.MediaAnswer != null)
            {
                this.MediaAnswer(this, new EventArgs<SDP_Message>(sdp));
            }
        }

        #endregion

        #endregion
        /// <summary>
        /// ////////////////////////////////////////////////////////////////////////////
        /// ////////////////////////////////////////////////////////////////////////////
        /// ////////////////////////////////////////////////////////////////////////////
        /// </summary>
        #region Ext

        public SIP_t_From From { get; private set; }
        public SIP_t_To To { get; private set; }
        public string CallID { get; private set; }

        public string LineCall
        {
            get { return  string.Format("{0}", this.RemoteUri); }
        }

        public string DisplayName //{ get; set; }
        { get
        {
         if (IsOutCall)
             return To != null ? To.Address.DisplayName : string.Empty;

         return From != null ? From.Address.DisplayName : string.Empty;
        }
        }
        private string filetemp
        {
            get
            {
                string f = Regex.Replace(RemoteUri.ToString(), "sip:", "", RegexOptions.IgnoreCase).Trim();
                f = Regex.Match(f, "^(?<num>.*?)@").Groups["num"].Value.Trim();
                return string.IsNullOrEmpty(f) ? "nan" : f;
            }
        }

        private string WavFileName { get; set; }

        private string[] tmpFiles;

        public int LineNumber { get; set; }

        public RTP_AddressSocket RtpAddressSocket { get; set; }
        /// <summary>
        /// Toggles call on hold state.
        /// </summary>
        /// <param name="call">SIP call.</param>
        /// <exception cref="ArgumentNullException">Is raised when <b>call</b> is null reference.</exception>
        public void ToggleCallOnHold()
        {
            bool putOnHold = true;
            if (Tags.ContainsKey("OnHold"))
            {
                putOnHold = !((bool)Tags["OnHold"]);
            }

            if (!Tags.ContainsKey(Private.Call.MediaSession)) return;
            MediaSession mediaSession = (MediaSession)Tags[Private.Call.MediaSession];
            // Because we try to modify session, we need to increase SDP version.
            mediaSession.LocalSdpVersion++;

            // Generate new SDP offer.
            SDP_Message sdpOffer = CreateSDP(mediaSession, RtpAddressSocket, putOnHold);

            SIP_Request reinvite = Dialog.CreateRequest(SIP_Methods.INVITE);
            reinvite.ContentType = "application/sdp";
            reinvite.Data = sdpOffer.ToByte();

            AutoResetEvent wait = new AutoResetEvent(false);
            SIP_ResponseReceivedEventArgs response = null;
            SIP_RequestSender requestSender = Dialog.CreateRequestSender(reinvite);

            requestSender.ResponseReceived +=
                new EventHandler<SIP_ResponseReceivedEventArgs>(delegate(object s, SIP_ResponseReceivedEventArgs e)
                {
                    response = e;
                });
            requestSender.Completed += new EventHandler(delegate(object s, EventArgs e) { wait.Set(); });
            requestSender.Error += new EventHandler<ExceptionEventArgs>(delegate(object s, ExceptionEventArgs e)
            {
                wait.Set();
                //if (TelSipError != null)
                //    TelSipError(this, new StrErrorEventArgs("SipOperation ToggleCallOnHold -> requestSender", e.Exception));
                OnError(e.Exception);
            });

            requestSender.Start();
            wait.WaitOne();

            // This never should happen.
            if (response == null)
            {
                //   MessageBox.Show(this, "Internal error.", "Error:", MessageBoxButtons.OK, MessageBoxIcon.Error);
            }
            // Remote party accepted offer.
            if (response.Response.StatusCodeType == SIP_StatusCodeType.Success)
            {
                // Store OnHold state flag.
                Tags["OnHold"] = putOnHold;
                mediaSession.MediaStreams.ForEach(x => x.RtpSession.Hold = putOnHold);
                SDP_Message sdpAnswer = SDP_Message.Parse(Encoding.UTF8.GetString(response.Response.Data));
                SetState(putOnHold ? CallState.Hold : CallState.Active);
            }
        }

        public void SetMicrophone(bool value)
        {
            if (Tags.ContainsKey(AudioSender))
            {
                RTP_AudioSender audioSender = Tags[AudioSender] as RTP_AudioSender;
                if (audioSender != null) audioSender.IsActive = value;
            }
        }
        /// <summary>
        /// Creates local SDP based on the specified media session.
        /// </summary>
        /// <param name="mediaSession">Media session.</param>
        /// <returns>Returns created SDP.</returns>
        /// <exception cref="ArgumentNullException">Is raised when <b>mediaSession</b> is null reference.</exception>
        public static SDP_Message CreateSDP(MediaSession mediaSession, RTP_AddressSocket rtpAddress, bool onHold)
        {
            if (mediaSession == null)
            {
                throw new ArgumentNullException("mediaSession");
            }

            if (rtpAddress == null)
            {
                throw new ArgumentNullException("rtpAddress");
            }

            SDP_Message sdpOffer = new SDP_Message();
            sdpOffer.Version = "0";
            sdpOffer.Origin = new SDP_Origin("-", sdpOffer.GetHashCode(), mediaSession.LocalSdpVersion, "IN", "IP4", mediaSession.LocalHost);
            sdpOffer.SessionName = "SIP Call";
            sdpOffer.Times.Add(new SDP_Time(0, 0));

            // Add media streams.
            foreach (MediaStream m in mediaSession.MediaStreams)
            {
                if (!m.IsUsed)
                {
                    SDP_MediaDescription media = new SDP_MediaDescription(m.MediaType, 0, 0, m.Protocol, m.MediaFormats.ToArray());
                    sdpOffer.MediaDescriptions.Add(media);
                }
                else
                {
                    
                    SDP_MediaDescription media = new SDP_MediaDescription(m.MediaType,
                        ((IPEndPoint)rtpAddress.RtpSocket.LocalEndPoint).Port,
                        ((IPEndPoint)rtpAddress.RtcpSocket.LocalEndPoint).Port, "RTP/AVP", m.MediaFormats.ToArray());

                    media.Connection = new SDP_Connection("IN", "IP4", m.PublicRtpEP.IP.ToString());
                    if (m.PublicRtpEP.ControlPort != (m.PublicRtpEP.DataPort + 1))
                    {
                      //  media.Attributes.Add(new SDP_Attribute("rtcp", m.PublicRtpEP.ControlPort.ToString()));
                    }
                    media.Attributes.Add(new SDP_Attribute("rtpmap", "0 PCMU/8000"));
                    media.Attributes.Add(new SDP_Attribute("rtpmap", "8 PCMA/8000"));
                    media.Attributes.Add(new SDP_Attribute("rtpmap", "101 telephone-event/8000"));
                    media.Attributes.Add(new SDP_Attribute("fmtp", "101 0-16"));
                    media.Attributes.Add(new SDP_Attribute("ptime", "20"));
                    if (m.RtpSession != null)
                    {
                        if (onHold)
                        {
                            media.Attributes.Add(new SDP_Attribute("sendonly", ""));
                        }
                        else
                        {
                            media.Attributes.Add(new SDP_Attribute("sendrecv", ""));
                        }
                    }
                    sdpOffer.MediaDescriptions.Add(media);
                }
            }

            return sdpOffer;
        }


        #region Запись в фаил

        public bool IsSaving { get; private set; }
        public void StartSaving()
        {
            string dir = Environment.GetFolderPath(Environment.SpecialFolder.MyMusic);
            WavFileName = string.Format("{0}\\{1}_{2:dd-MM-yyyy HH-mm}.wav", dir, filetemp, DateTime.Now);
            tmpFiles = new string[2] { string.Format("{0}_in.wav", filetemp), string.Format("{0}_out.wav", filetemp) };
            try
            {
                if (Tags[AudioReceiver] is RTP_AudioPlayer)
                {
                    RTP_AudioPlayer receiver = (RTP_AudioPlayer)Tags[AudioReceiver];
                    receiver.StartSaving(tmpFiles[0]);

                    RTP_AudioSender audioSender = (RTP_AudioSender)Tags[AudioSender];
                    audioSender.StartSaving(tmpFiles[1]);
                }
                IsSaving = true;
            }
            catch (Exception error)
            {
                OnError(error);
            }
        }

        public void StopSaving()
        {
            try
            {
                if (Tags[AudioReceiver] is RTP_AudioPlayer)
                {
                    RTP_AudioPlayer receiver = (RTP_AudioPlayer)Tags[AudioReceiver];
                    receiver.StopSaving();

                    RTP_AudioSender audioSender = (RTP_AudioSender)Tags[AudioSender];
                    audioSender.StopSaving();
                }
                IsSaving = false;
            }
            catch (Exception error)
            {
                OnError(error);
            }
        }

        public void MixInOut()
        {
            try
            {
                WAVFile.MergeAudioFiles(tmpFiles, WavFileName, Path.GetTempPath());
                foreach (var item in tmpFiles)
                {
                    File.Delete(item);
                }
            }
            catch (Exception error)
            {
                OnError(error);
            }
        }
        #endregion

        #region Tone
        public void RunBusy()
        {
            m_pAudioPlayer = new WavePlayer(AudioOutStram);
            m_pAudioPlayer.Valume = SignalVolume;
            m_pAudioPlayer.Play(ResManager.GetStream("busy.wav"), int.MaxValue);

            //m_player = new SoundPlayer(ResManager.GetStream("busy.wav"));
            //m_player.PlayLooping();
        }
        public void TonePlay(ClickNumberEventArgs but, Stream stream)
        {
            RTP_AudioSender audioSender = (RTP_AudioSender)Tags[AudioSender];
            audioSender.ToneSend(but.NumberCode, stream);
            audioSender.Dispose();
        }

        public bool IsWasActive { get; private set; }

        /// <summary>
        /// Признак исходящего соединения <b>true</b>, при входящем <b>false</b>
        /// </summary>
        public bool IsOutCall { get; set; }
        public bool IsTerminate { get; private set; }

        #endregion
        #endregion

    }
}